package com.weareint.common.advices;

import com.weareint.common.constants.Constants;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * <pre>
 *     日志记录器 只在dev profile模式下生效
 * </pre>
 */
@Aspect
@Component
@Profile(Constants.DEV_PROFILE)
public class LoggingAspect {

    private final Environment env;

    public LoggingAspect(Environment env) {
        this.env = env;
    }

    /**
     * <pre>
     *     切点为以下三个注解标记的类
     * </pre>
     */
    @Pointcut(
            "within(@org.springframework.stereotype.Service *)"
                    + " || within(@org.springframework.web.bind.annotation.RestController *)"
                    + " || within(@org.springframework.stereotype.Component *)")
    public void springBeanPointcut() {
    }

    /**
     * <pre>
     *     切点为此包下的所有对象
     * </pre>
     */
    @Pointcut("within(com.weareint..*)")
    public void applicationPackagePointcut() {
    }

    /**
     * <pre>
     *      获取正在被切的对象的Logger
     * </pre>
     * @param joinPoint
     * @return
     */
    private Logger logger(JoinPoint joinPoint) {
        return LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringTypeName());
    }

    /**
     * <pre>
     *      异常后置处理器
     * </pre>
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(
            pointcut = "applicationPackagePointcut() && springBeanPointcut()",
            throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        // 只有dev profile时才打印异常信息
        if (env.acceptsProfiles(Profiles.of(Constants.DEV_PROFILE))) {
            logger(joinPoint)
                    .error(
                            "Exception in {}() with cause = \'{}\' and exception = \'{}\'",
                            joinPoint.getSignature().getName(),
                            e.getCause() != null ? e.getCause() : "NULL",
                            e.getMessage(),
                            e);
        } else {
            logger(joinPoint)
                    .error(
                            "Exception in {}() with cause = {}",
                            joinPoint.getSignature().getName(),
                            e.getCause() != null ? e.getCause() : "NULL");
        }
    }

    /**
     * <pre>
     *      环绕处理器
     * </pre>
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("applicationPackagePointcut() && springBeanPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Logger log = logger(joinPoint);
        if (log.isDebugEnabled()) {
            log.debug(
                    "Enter: {}() with argument[s] = {}",
                    joinPoint.getSignature().getName(),
                    Arrays.toString(joinPoint.getArgs()));
        }
        try {
            Object result = joinPoint.proceed();
            if (log.isDebugEnabled()) {
                log.debug(
                        "Exit: {}() with result = {}", joinPoint.getSignature().getName(), result);
            }
            return result;
        } catch (IllegalArgumentException e) {
            log.error(
                    "Illegal argument: {} in {}()",
                    Arrays.toString(joinPoint.getArgs()),
                    joinPoint.getSignature().getName());
            throw e;
        }
    }
}
